﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using Cloak.Globalization;
using Ruleweaver.Conditions;
using Ruleweaver.Rules;

namespace Ruleweaver.Annotation
{
	internal sealed class MetadataClassReferenceFixUp : RuleVisitor
	{
		private Type _targetType;

		internal Condition ChangeToTargetTypeReferences(Condition condition, Type targetType)
		{
			_targetType = targetType;

			var visitedRule = Visit(condition.Rule);

			if(visitedRule != condition.Rule)
			{
				condition = new Condition(visitedRule, targetType, condition.Key.Name);
			}

			return condition;
		}

		protected override Rule VisitField(MemberRule fieldRule)
		{
			return GetMemberRule<FieldInfo>(fieldRule, (field, rule) => Rule.Field(field, rule));
		}

		protected override Rule VisitProperty(MemberRule propertyRule)
		{
			return GetMemberRule<PropertyInfo>(propertyRule, (property, rule) => Rule.Property(property, rule));
		}

		protected override Rule VisitMethod(MemberRule methodRule)
		{
			return GetMemberRule<MethodInfo>(methodRule, (method, rule) => Rule.Method(method, rule));
		}

		private MemberRule GetMemberRule<TMember>(MemberRule classMemberRule, Func<TMember, Rule, MemberRule> createRule)
			where TMember : MemberInfo
		{
			MemberInfo targetMember;

			switch(classMemberRule.Type)
			{
				case RuleType.Field:
				case RuleType.Property:
					targetMember = GetTargetFieldOrProperty(classMemberRule);
					break;
				case RuleType.Method:
					targetMember = GetTargetMethod(classMemberRule);
					break;
				default:
					throw new RuleweaverException(Resources.UnsupportedMetadataClassMemberType.FormatInvariant(
						classMemberRule.Type,
						classMemberRule.Member.ReflectedType.FullName,
						classMemberRule.Member.Name));
			}

			return createRule((TMember) targetMember, classMemberRule.Rule);
		}

		private MemberInfo GetTargetFieldOrProperty(MemberRule classMemberRule)
		{
			var targetMembers = _targetType.GetMember(
				classMemberRule.Member.Name,
				classMemberRule.Member.MemberType,
				BindingFlags.Public | BindingFlags.Instance);

			// Properties and fields only have one possible name match

			var targetMember = targetMembers.SingleOrDefault();

			if(targetMember == null)
			{
				ThrowOnMissingMember(classMemberRule);
			}

			return targetMember;
		}

		private MemberInfo GetTargetMethod(MemberRule classMemberRule)
		{
			var targetMethod = _targetType.GetMethod(
				classMemberRule.Member.Name,
				BindingFlags.Public | BindingFlags.Instance,
				null,
				Type.EmptyTypes,
				null);

			if(targetMethod == null)
			{
				ThrowOnMissingMember(classMemberRule);
			}

			return targetMethod;
		}

		private void ThrowOnMissingMember(MemberRule classMemberRule)
		{
			throw new AnnotationException(Resources.NoCorrespondingMemberOnTargetType.FormatInvariant(
				classMemberRule.Member.ReflectedType.FullName,
				classMemberRule.Member.Name,
				_targetType));
		}
	}
}